import 'dart:math';

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:r_scan/r_scan.dart';

class ScanPage extends StatefulWidget {
  const ScanPage({Key? key}) : super(key: key);

  @override
  State<ScanPage> createState() => _ScanPageState();
}

class _ScanPageState extends State<ScanPage> {
  List<RScanCameraDescription>? _cameras;
  RScanCameraController? _controller;
  bool isFirst = true;
  bool isDetected = false;

  @override
  initState() {
    super.initState();
    initCamera();
  }

  Future<bool> canReadStorage() {
    return Permission.storage.request().isGranted;
  }

  Future<bool> canOpenCamera() async {
    return Permission.camera.request().isGranted;
  }

  initCamera() async {
    _cameras = await availableRScanCameras();

    if (_cameras!.isNotEmpty) {
      _controller = RScanCameraController(
        _cameras![0],
        RScanCameraResolutionPreset.max,
      )
        ..addListener(() {
          final result = _controller?.result;
          if (result != null) {
            if (isFirst) {
              Navigator.of(context).pop(result);
              isFirst = false;
            }
          }
        })
        ..initialize().then((_) {
          if (!mounted) {
            return;
          }
          setState(() {
            isDetected = true;
          });
        });
    } else {
      setState(() {
        isDetected = true;
      });
    }
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!isDetected) {
      return Container();
    }
    if (_cameras == null || _cameras!.isEmpty) {
      return Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: const Text('not have available camera'),
        ),
      );
    }
    if (_controller?.value.isInitialized == null ||
        !_controller!.value.isInitialized!) {
      return Container();
    }
    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        children: <Widget>[
          ScanImageView(
            child: AspectRatio(
              aspectRatio: _controller!.value.aspectRatio,
              child: RScanCamera(_controller!),
            ),
          ),
          Align(
              alignment: Alignment.bottomCenter,
              child: FutureBuilder(
                future: getFlashMode(),
                builder: _buildFlashBtn,
              ))
        ],
      ),
    );
  }

  Future<bool> getFlashMode() async {
    bool isOpen = false;
    try {
      isOpen = (await _controller!.getFlashMode()) ?? false;
    } catch (_) {}
    return isOpen;
  }

  Widget _buildFlashBtn(BuildContext context, AsyncSnapshot<bool> snapshot) {
    return snapshot.hasData
        ? Padding(
            padding: EdgeInsets.only(
                bottom: 24 + MediaQuery.of(context).padding.bottom),
            child: IconButton(
                icon: Icon(
                    snapshot.data == null ? Icons.flash_on : Icons.flash_off),
                color: Colors.white,
                iconSize: 46,
                onPressed: () {
                  if (snapshot.data == null) {
                    _controller?.setFlashMode(false);
                  } else {
                    _controller?.setFlashMode(true);
                  }
                  setState(() {});
                }),
          )
        : Container();
  }
}

class ScanImageView extends StatefulWidget {
  final Widget? child;

  const ScanImageView({Key? key, this.child}) : super(key: key);

  @override
  State<ScanImageView> createState() => _ScanImageViewState();
}

class _ScanImageViewState extends State<ScanImageView>
    with TickerProviderStateMixin {
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 1000));
    controller.repeat(reverse: true);
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: controller,
        builder: (BuildContext context, Widget? child) => CustomPaint(
              foregroundPainter:
                  _ScanPainter(controller.value, Colors.white, Colors.green),
              willChange: true,
              child: widget.child,
            ));
  }
}

class _ScanPainter extends CustomPainter {
  final double value;
  final Color borderColor;
  final Color scanColor;

  _ScanPainter(this.value, this.borderColor, this.scanColor);

  Paint? _paint;

  @override
  void paint(Canvas canvas, Size size) {
    if (_paint == null) {
      initPaint();
    }
    double width = size.width;
    double height = size.height;

    double minSize = min(width, height);

    double boxSize = minSize * 2 / 3;

    double padding = (minSize - boxSize) / 2;
    double offset = boxSize + padding;
    _paint!.color = borderColor;
    final rect = Rect.fromLTWH(padding, padding, boxSize, boxSize);
    canvas.drawRect(rect, _paint!);

    _paint!.strokeWidth = 3;

    Path path1 = Path()
      ..moveTo(padding, padding + 10)
      ..lineTo(padding, padding)
      ..lineTo(padding + 10, padding);
    canvas.drawPath(path1, _paint!);
    Path path2 = Path()
      ..moveTo(padding, offset - 10)
      ..lineTo(padding, offset)
      ..lineTo(padding + 10, offset);
    canvas.drawPath(path2, _paint!);
    Path path3 = Path()
      ..moveTo(offset, offset - 10)
      ..lineTo(offset, offset)
      ..lineTo(offset - 10, offset);
    canvas.drawPath(path3, _paint!);
    Path path4 = Path()
      ..moveTo(offset, padding + 10)
      ..lineTo(offset, padding)
      ..lineTo(offset - 10, padding);
    canvas.drawPath(path4, _paint!);

    _paint!.color = scanColor;

    final scanRect = Rect.fromLTWH(
        padding + 10, padding + 10 + (value * (boxSize - 20)), boxSize - 20, 3);

    _paint!.shader = const LinearGradient(
      colors: [Colors.white54, Colors.white, Colors.white54],
      stops: [0.0, 0.5, 1],
    ).createShader(scanRect);
    canvas.drawRect(scanRect, _paint!);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }

  void initPaint() {
    _paint = Paint()
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1
      ..isAntiAlias = true
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round;
  }
}
